
#include <event2/event.h>
#include <event2/event_struct.h>

#include <openssl/ssl.h>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <stdio.h>
#include <string.h>

/************************************************************/

#include <sys/syscall.h>
__thread int t_cachedTid = 0;
int current_tid(void)
{
    if (t_cachedTid == 0)
    {
        t_cachedTid = (int)syscall(SYS_gettid);
    }

    return t_cachedTid;
}

void log_write(const char *file, int line, const char *fmt, ...)
{
    struct timeval tv;
    time_t tt;
    struct tm tm;
    const char *file_ptr;
    
    va_list ap;
    
    file_ptr = strrchr(file, '/');
    if (file_ptr) { file_ptr++; }
    else { file_ptr = file; }
    
    gettimeofday(&tv, NULL);
    tt = tv.tv_sec;
    localtime_r(&tt, &tm);

    fprintf(stdout, "[%u-%02u-%02u %02u:%02u:%02u.%03u][%s:%d][%d]",
        tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (unsigned)(tv.tv_usec/1000), 
        file_ptr, line,
        current_tid());

    va_start(ap, fmt);
    vfprintf(stdout, fmt, ap);
    va_end(ap);

    fprintf(stdout, "\n");
}
#define log(arg...) log_write(__FILE__, __LINE__, ##arg)

/************************************************************/

enum tls_state
{
    TLS_STATE_HANDSHAKE,
    TLS_STATE_SNDRCV,
};

/************************************************************/

typedef struct tls_client{
    int fd;
    struct event_base *loop;
    struct event fd_evt;
    
    SSL_CTX *ssl_ctx;
    SSL *ssl;
    enum tls_state tls_state;
} tls_client_t;

static
void on_tls_client_event(evutil_socket_t fd, short event, void *arg)
{
    tls_client_t *tls_client = (tls_client_t*)arg;
    int ssl_ret;
    int ssl_error;

    if (tls_client->tls_state == TLS_STATE_HANDSHAKE)
    {
        if (event & EV_CLOSED)
        {
            log("tls client handshake failed: IO failure, sys error: %d", errno);
        }
        else
        {
            ssl_ret = SSL_connect(tls_client->ssl);
            ssl_error = SSL_get_error(tls_client->ssl, ssl_ret);
            if (ssl_ret == 1)
            {
                tls_client->tls_state = TLS_STATE_SNDRCV;
                log("=== tls client handshake OK ===");
            }
            else if (ssl_ret < 0 && (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE))
            {
                /* keep going and nothing todo */
            }
            else
            {
                log("tls client handshake failed: fatal ssl error: %d, sys error: %d", ssl_error, errno);
            }
        }
    }
    else if (tls_client->tls_state == TLS_STATE_SNDRCV)
    {
        /* TODO */
    }
    
    return;
}

static
int tls_client_start(tls_client_t* tls_client, int fd, struct event_base *loop)
{
    int ssl_ret;
    int ssl_error;
    
    tls_client->fd = fd;
    tls_client->loop = loop;

    event_assign(&tls_client->fd_evt, loop, tls_client->fd, 
        EV_READ | EV_PERSIST, on_tls_client_event, tls_client);
    event_add(&tls_client->fd_evt, NULL);

    tls_client->tls_state = TLS_STATE_HANDSHAKE;

    tls_client->ssl_ctx = SSL_CTX_new(TLS_client_method());;
    SSL_CTX_set_mode(tls_client->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_AUTO_RETRY);
    tls_client->ssl = SSL_new(tls_client->ssl_ctx);
    SSL_set_fd(tls_client->ssl, fd);

    ssl_ret = SSL_connect(tls_client->ssl);
    ssl_error = SSL_get_error(tls_client->ssl, ssl_ret);
    if (ssl_ret == 1)
    {
        tls_client->tls_state = TLS_STATE_SNDRCV;
        log("=== tls client handshake OK ===");
    }
    else if (ssl_ret < 0 && (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE))
    {
        /* keep going and nothing todo */
    }
    else
    {
        log("failed to start tls client handshake: fatal ssl error: %d, sys error: %d", ssl_error, errno);
        return -1;
    }

    return 0;
}

static
void tls_client_stop(tls_client_t* tls_client)
{
    if (tls_client->tls_state == TLS_STATE_SNDRCV)
    {
        SSL_shutdown(tls_client->ssl);
    }

    SSL_free(tls_client->ssl);
    SSL_CTX_free(tls_client->ssl_ctx);
    event_del(&tls_client->fd_evt);

    return;
}

/************************************************************/

typedef struct tls_server{
    int fd;
    struct event_base *loop;
    struct event fd_evt;
    
    SSL_CTX *ssl_ctx;
    SSL *ssl;
    enum tls_state tls_state;
} tls_server_t;

static
void on_tls_server_event(evutil_socket_t fd, short event, void *arg)
{
    tls_server_t *tls_server = (tls_server_t*)arg;
    int ssl_ret;
    int ssl_error;

    if (tls_server->tls_state == TLS_STATE_HANDSHAKE)
    {
        if (event & EV_CLOSED)
        {
            log("tls server handshake failed: IO failure, sys error: %d", errno);
        }
        else
        {
            ssl_ret = SSL_accept(tls_server->ssl);
            ssl_error = SSL_get_error(tls_server->ssl, ssl_ret);
            if (ssl_ret == 1)
            {
                tls_server->tls_state = TLS_STATE_SNDRCV;
                log("=== tls server handshake OK ===");
            }
            else if (ssl_ret < 0 && (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE))
            {
                /* keep going and nothing todo */
            }
            else
            {
                log("tls server handshake failed: fatal ssl error: %d, sys error: %d", ssl_error, errno);
            }
        }
    }
    else if (tls_server->tls_state == TLS_STATE_SNDRCV)
    {
        /* TODO */
    }

    return;
}

static
int tls_server_start
(
    tls_server_t* tls_server, int fd, struct event_base *loop,
    const char* ca_file, const char *private_key_file, const char *ca_pwd
)
{
    int ssl_ret;
    int ssl_error;
    
    tls_server->fd = fd;
    tls_server->loop = loop;

    event_assign(&tls_server->fd_evt, loop, tls_server->fd, 
        EV_READ | EV_PERSIST, on_tls_server_event, tls_server);
    event_add(&tls_server->fd_evt, NULL);

    tls_server->tls_state = TLS_STATE_HANDSHAKE;

    tls_server->ssl_ctx = SSL_CTX_new(TLS_server_method());;
    SSL_CTX_set_mode(tls_server->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_AUTO_RETRY);
    tls_server->ssl = SSL_new(tls_server->ssl_ctx);
    SSL_set_fd(tls_server->ssl, fd);

    ssl_ret = SSL_use_certificate_file(tls_server->ssl, ca_file, SSL_FILETYPE_PEM);
    if (ssl_ret != 1)
    {
        ssl_error = SSL_get_error(tls_server->ssl, ssl_ret);
        log("tls server: failed to load CA, ssl errno: %d", ssl_error);
        return -1;
    }

    if (private_key_file && (ssl_ret = SSL_use_PrivateKey_file(tls_server->ssl, private_key_file, SSL_FILETYPE_PEM)) != 1)
    {
        ssl_error = SSL_get_error(tls_server->ssl, ssl_ret);
        log("tls server: failed to load CA private key, ssl errno: %d", ssl_error);
        return -1;
    }

    ssl_ret = SSL_check_private_key(tls_server->ssl);
    if (ssl_ret != 1)
    {
        ssl_error = SSL_get_error(tls_server->ssl, ssl_ret);
        log("tls server: check ssl private key failed, ssl errno: %d", ssl_error);
        return -1;
    }

    return 0;
}

static
void tls_server_stop(tls_server_t* tls_server)
{
    if (tls_server->tls_state == TLS_STATE_SNDRCV)
    {
        SSL_shutdown(tls_server->ssl);
    }

    SSL_free(tls_server->ssl);
    SSL_CTX_free(tls_server->ssl_ctx);
    event_del(&tls_server->fd_evt);

    return;
}

/************************************************************/

int main(int argc, char *argv[])
{
    int fds[2];
    struct event_base *loop;

    tls_client_t tls_client;
    tls_server_t tls_server;
    
    if (argc < 4)
    {
        printf("usage: %s <ca file> <key file> <ca pwd>\n", argv[0]);
        return -1;
    }
    
    SSL_library_init();
    SSL_load_error_strings();

    /* 给 openssl 底层数据交换的 fd 必须是双向读写的，因而只能用 socketpair() 而不是 pipe() */
    socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, fds);

    loop = event_base_new();

    tls_client_start(&tls_client, fds[0], loop);
    tls_server_start(&tls_server, fds[1], loop, argv[1], argv[2], argv[3]);

    event_base_loop(loop, 0);

    tls_client_stop(&tls_client);
    tls_server_stop(&tls_server);

    event_base_free(loop);

    close(fds[0]);
    close(fds[1]);

    return 0;
}